[None][feat] Self-sampling GVR V2 prefill indexer top-K - #18702
Conversation
|
/bot run |
|
PR_Github #71439 [ run ] triggered by Bot. Commit: |
|
PR_Github #71439 [ run ] completed with state
|
a923cc0 to
be15dca
Compare
|
/bot run |
|
PR_Github #71465 [ run ] triggered by Bot. Commit: |
|
/bot run --reuse-test --disable-fail-fast Merged |
|
PR_Github #71476 Bot args parsing error: usage: /bot [-h] |
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #71487 [ run ] triggered by Bot. Commit: |
|
PR_Github #71465 [ run ] completed with state |
|
PR_Github #71487 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
1 similar comment
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #71543 [ run ] triggered by Bot. Commit: |
|
PR_Github #71543 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #71654 [ run ] triggered by Bot. Commit: |
|
Refreshed the prefill numbers with Nsight (pure kernel GPU time, NVTX projection, cold-L2) instead of CUDA-event wall time, and swept batch as well as window — the event-based version I first posted over-smoothed a real BS dependence. Same synthetic setup (there's no captured prefill data — the §C/§D prefill streams are corrupt — so batches are built from real §G decode rows with causal / lower-triangular windows). Correctness is unchanged: GVR matches the CUDA radix and
(Pro tracks Flash.) The crossover moves with both window and batch:
So a length-gated prefill dispatch holds, but the threshold should also look at batch — near the crossover window, keep radix for very small batches. Single B200, synthetic distributions (real per-row logits and length range, no true intra-request correlation); preliminary, not a ship verdict. |
|
PR_Github #71654 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #71674 [ run ] triggered by Bot. Commit: |
|
PR_Github #71674 [ run ] completed with state |
|
/bot run --disable-fail-fast |
|
PR_Github #71726 [ run ] triggered by Bot. Commit: |
|
PR_Github #71726 [ run ] completed with state |
|
/bot run --disable-fail-fast |
|
PR_Github #72066 [ run ] triggered by Bot. Commit: |
|
PR_Github #72047 [ run ] completed with state |
Every comment or docstring this PR added that ran past three lines is cut to the invariant it protects (18 blocks, no code change). Made-with: Claude Code (Fable 5.1) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: longcheng-nv <243710427+longcheng-nv@users.noreply.github.com>
|
/bot run --disable-fail-fast |
|
PR_Github #72079 [ run ] triggered by Bot. Commit: |
|
PR_Github #72066 [ run ] completed with state |
|
PR_Github #72079 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #72172 [ run ] triggered by Bot. Commit: |
|
PR_Github #72172 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #72206 [ run ] triggered by Bot. Commit: |
|
PR_Github #72206 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #72217 [ run ] triggered by Bot. Commit: |
|
PR_Github #72217 [ run ] completed with state
|
|
/bot run --reuse-test --disable-fail-fast |
|
PR_Github #72249 [ run ] triggered by Bot. Commit: |
|
PR_Github #72249 [ run ] completed with state |
reasonsolo
left a comment
There was a problem hiding this comment.
No disagg code change. Approved.
zongfeijing
left a comment
There was a problem hiding this comment.
LGTM from the kernel side
Description
Stacked on #18446. Extends the hint-free self-sampling GVR V2 indexer top-K
(introduced for decode by #18410/#18446) to the prefill phase, so a DSA
model that selects self-sampling for decode uses the same engine for prefill
instead of the CUDA radix path — one config, one kernel family, one warmup.
Dispatch is unchanged and needs no new config/API field: prefill uses the
self-sampling engine on exactly the layers where the two-level dispatch
(
enable_heuristic_topk×use_self_sampling_topk) already picks it fordecode. The temporal-hint (V1) layers keep the exact radix prefill (that
engine has no prefill form). The
TopK.forward(..., is_prefill=True, row_starts=..., row_ends=...)interface is unchanged.Kernel (
gvr_topk_decode_self_sampling.py)A constexpr
prefillmode onGvrMainKernelonly (the streaming family, whichis correct for any row length). Per row
rit selects the top-K oflogits[r, ks:ke](ks=row_starts[r],ke=row_ends[r], compressed columnunits), writes the LOCAL frame (
column - ks) with a trailing-1pad, andemits identity
0..nv-1whennv = ke-ks <= k— matching the radix short-rowcontract.
ks/keride the otherwise-unusedpre_idx/kv_lensABI slots(hint-free never reads them), so the kernel signature is byte-identical; the
prefill compile is a distinct cache key.
The base is rounded down to a 16B boundary (
col0 = ks & ~3) so the vectorloads stay aligned; the ≤3
lead = ks & 3lanes are masked positionally (aone-bit clear of the
i0==0classification mask, a valid-lane substitution inthe sample fold, and
i >= leadguards on the two whole-row escape sweeps),never with a materialized
-inf(which would drive the sample histogram indexout of bounds and mis-rank a genuine
-inftie class). The row-extent clampstighten to the last in-window float4, so the kernel reads exactly
[r*npad + col0, r*npad + ke)with no dependence on producer slack. Outputindices subtract
leadat the six write sites. Every edit isconst_expr-gatedon the prefill flag; the decode/legacy codegen is unchanged (the full decode
suite passes bit-for-bit).
Host / module / framework
run_prefill(logits, row_starts, row_ends, indices, max_row_len=None)— samevalidation style as
run_varlenbut keyed onstride(0)for all row counts(DeepGEMM prefill rows are 1024B-aligned with ≥256-float slack, so the
single-row
shape[1]rule that would reject odd-width tiles does not apply),no device reads (envelope from
shape[1]), and it slabs launches to ≤32768rows so
gridDim.ynever overflows. ForcesR=1(single CTA per row, noSPLIT/workspace); the launcher cache is keyed on
(row tier, k, envelope bucket)so arbitrary q-tile row counts do not grow it.warmup_prefill— compiles the ≤6 engines per k before serving (placedbefore the DeepGEMM decode-stride guard in
warmup_selfsampling_topk, so anodd
msl_ccannot skip it); best-effort under the existing OOM guard.TopK._forward_prefillgains aCUTE_DSL_GVRbranch: all-short tiles(
shape[1] <= k) take the exact radix identity path silently; the hardware-format gate (fp32, unit inner stride, float4 row stride, 16B base) routes to
the engine with a one-time
info_once; a miss falls back to radix with awarning_once.CUTE_DSL_RADIXstill raisesNotImplementedError.CUTE_DSL_GVRfor prefill iff it selects it for decodeand the self-sampling prerequisites hold; no
llm_args/API change.Prefill top-K is never CUDA-graph captured (
mla_dsa_attn_inplaceis apiecewise boundary op); there are no host syncs; PDL parity with the radix
prefill is none. Disaggregated context-only servers are covered by the
unconditional warmup hook. The port is compression-ratio agnostic (
ks/keare already compressed), so DeepSeek-V3.2 (cr=1, K=2048), V4 Flash (cr=4,
K=512) and V4 Pro (cr=4, K=1024) share one prefill path.
Validation
Kernel/host exactness against a tie-aware radix-parity reference on a B200
(SM100), per-row
[ks, ke)windows on DeepGEMM-strided storage:ks % 4in{1,2,3} and
+inf/NaN/3e38/-infpoison outside the window; short rows(
nvin {0, 1, k-1, k, k+1}); all-equal / two-valued degenerate paths;odd-width single-row tiles; the
-inftie class crossed with a misalignedlead (asserts no negative index leaks); a >65535-row slab. K in
{512, 1024, 2048}.
fallthrough to radix, temporal-mode fallthrough, output-width assertion.
buckets after warmup; the prefill compile key is distinct from the decode
varlen key.
layers keep radix; the warmup prefill leg fires (including for an odd
msl_c).GSM8K accuracy — end-to-end parity (B200 TEP8, 5-shot)
End-to-end
trtllm-eval gsm8k(tp8 ep8, one served config per arm). Anarmproofgate asserts each arm actually engaged the intended top-K path —decode/prefill engagement is grepped from the served logs (
viol=0), so the"V2 prefill" numbers are verified to run the new kernel, not silently fall back
to radix. The self-sampling prefill (this PR) matches both the exact radix
baseline and the pre-PR "V2 decode + radix prefill" state within GSM8K sampling
noise (stderr ±~0.6). Scores are
exact_matchaveraged over flexible-extract +strict-match, normalized to 0–100.
All three
pf_onruns are confirmedprefill_engaged=yes— the V2 prefillkernel actually ran and the score still lands on top of both baselines, so the
prefill top-K swap is accuracy-neutral across DSV3.2, V4-Flash and V4-Pro. Each
model runs its own
index_topk(Flash 512 / Pro 1024 / DSV3.2 2048).Preliminary kernel microbenchmark
A single-B200 cold-L2 CUDA-event sweep (random Gaussian logits, 20 reps/shape) pairs
the radix
topKPerRowPrefillagainst the DSL prefill kernel across 14 prefill shapes(DSv3.2 / V4, K in {512,1024,2048}, rows 64..32768, n_kv 4096..262144). The
self-sampling kernel is faster on every shape (median ~4×, worst 0.859×), so no
N-gate to radix is added. This is a gross-regression sanity check on random data;
the in-pipeline nsys numbers below are what govern the ship decision.
Operator-level top-K speedup vs the radix CUDA path
Test env: B200 (SM100), 8 GPUs,
tp8 ep8, PyTorch backend, chunked prefill(chunk=8192), KV fp8. An nsys A/B — pf_on GVR V2 selection kernel
GvrMainKernelvs pf_off radix
topKPerRowPrefill<K>— isolated by an exact kernel-nameset-difference (~103–110 kernel types per run; the two selection kernels are the
only differentiator, verified). Numbers are rank-0 GPU kernel duration of the
prefill window (NVTX-validated), dummy-AR excluded from the DEP8 denominators.
Per-prefill-window rank-0 kernel time (radix baseline → V2), then the speedup:
topKPerRowPrefill<K>GvrMainKernel(this PR)The V2 selection kernel is 1.84–2.61× faster than the production radix kernel
(e.g. Pro DEP8/990K: radix 3071 ms → V2 1177 ms = 2.61×). The ratio grows with
context/candidate count (radix
topKPerRowPrefillscales worse with K and candidates;V2 self-sampling is ~K-invariant — Pro K=512→1024 moved it only 2.68×→2.61×).
The share is config-dependent: for the same Flash it goes 0.60%→3.43% as ISL
grows (262K→990K) and attention-DP removes the ~33% NCCL comms from the denominator
(TEP8→DEP8). Because top-K selection stays ≤3.4% of prefill, the 1.84–2.61× kernel
win translates to a modest few-% end-to-end gain at throughput scale (serving results
below), not a large e2e speedup.
Where the prefill time goes (DEP8/990016, dummy-AR-excluded), rank-0 kernel
work-share — the dominant kernel is the indexer MQA logits, not the top-K
selection, and at DEP8 there is no real TP AllReduce:
Full per-kernel breakdowns + all trace paths:
indexer_topk_op_bench/op51_gvr_v2_prefill/GVR_V2_TOPK_E2E_AND_OPERATOR_REPORT.html.No DSV3.2 (cr=1) layered breakdown: its operator share is unmeasurable under nsys
in this environment — profiling the DSA algorithm under attention-DP deadlocks CUPTI
(verified it runs cleanly without nsys; TEP8+nsys separately hits an NVRTC
cuda.hgap). Its e2e numbers (below) are unaffected. By cr=1 scaling (4× candidatesvs cr=4) its indexer-logits + top-K shares would be higher than Flash/Pro.
End-to-end serving throughput (max-config: DEP8, long-ISL, batched, 3 reps)
Because top-K selection is only 2.4–3.4% of prefill, its end-to-end effect surfaces in a
throughput config, not a BS=1 latency one (BS=1 is TTFT-neutral — full table in the
report). This config maximizes the prefill top-K share: attention-DP (DEP8, no TP
AllReduce), each model's max ISL, batched. Test env: B200 (SM100) 8 GPUs,
tp8 ep8+enable_attention_dp=true, chunked prefill, KV fp8, OSL=512, 3-rep median, all armsarmproof-verified; concurrency = the max that fits (Flash/DSV3.2 32, Pro 8). The PR'schange is radix prefill (pf_off) → V2 prefill (pf_on) — both share V2 decode, so this
isolates the prefill top-K engine. Raw values first, then the speedup:
The V2 prefill port gives a +2.9–4.5% throughput / −3.0–4.4% TTFT end-to-end gain over
radix prefill across all three models — a real (if modest) benefit at scale, consistent
with the 2.6× kernel speedup applied to top-K's ≤3.4%-of-prefill share. (Against the exact
radix decode+prefill baseline, V2 overall is ~neutral here, 0.998–1.014× throughput — at
this high concurrency the prefill-interleaved pipeline masks the decode benefit; per-arm
tables in the report.)
Test configuration (max-config e2e)
B200 (SM100) × 8, PyTorch backend,
trtllm-bench throughput --streaming, upstream-HFFP4+FP8-mixed weights, 3 reps (median). Arms: radix (
enable_heuristic_topk=false) ·pf_off (V2 decode + radix prefill,
=true+TRTLLM_GVR_PREFILL_TOPK=0, an e2e-harness-only overlay switch that is not part of this PR) · pf_on(V2 decode + V2 prefill,
=true).tp8 ep8 pp1 cp1·enable_attention_dp=true(DEP8)max_position_embeddingsceiling) / 512dtype=fp8,tokens_per_block128 (V4)/64 (V3.2),free_gpu_memory_fraction=0.8,enable_block_reuse=falsemax_num_tokens=131072; V3.2backend=TRTLLMalgorithmdeepseek_v4/dsa;index_topk512/1024/2048; cr 4/4/1;window_size=128TRTLLM_ENABLE_DUMMY_ALLREDUCE=1(a benchmark sync artifact; excluded from the nsys shares)The BS=1 latency e2e (SWE-bench-64K + random, ISL 262144/65536, TEP8) — where prefill
top-K is TTFT-neutral and the V2 decode benefit shows in TPOT — is in the standalone
report.
Status
Done: kernel + host + module + indexer + metadata (decode codegen unchanged);
kernel/host exactness (39 cases) + prefill UT (28) + CPU dispatch + warmup
no-re-JIT. End-to-end validated on B200 (8 GPU) across DSV3.2 / V4-Flash / V4-Pro:
GSM8K parity (accuracy-neutral); nsys operator profile (top-K selection 1.84–2.61×
the radix kernel, ≤3.4% of prefill, MQA logits dominant); BS=1 serving TTFT-neutral;
and a max-config (DEP8, long-ISL, batched) throughput run where the prefill port adds
+2.9–4.5% throughput / −3.0–4.4% TTFT.
Rebased onto
mainafter #18446 merged (85d9871); the diff is the prefilldelta only. Follow-ups folded in after review: the warmup hook builds the DSA
metadata itself when no warmup forward ran (context-only / draft / guided-
decoder engines), an engine missed by warmup falls back to radix instead of
raising under CUDA graph capture, and GPU tests pin the DeepGEMM logits row
stride the engine's format gate relies on.
PR Checklist
Please review the following before submitting your PR:
PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.
PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.
Test cases are provided for new code paths (see test instructions)
If PR introduces API changes, an appropriate PR label is added - either
api-compatibleorapi-breaking. Forapi-breaking, includeBREAKINGin the PR title.Any new dependencies have been scanned for license and vulnerabilities
CODEOWNERS updated if ownership changes
Documentation updated as needed
Update tava architecture diagram if there is a significant design change in PR.
The reviewers assigned automatically/manually are appropriate for the PR.
Please check this after reviewing the above items as appropriate for this PR.
GitHub Bot Help
To see a list of available CI bot commands, please comment
/bot help.Dev Engineer Review
QA Engineer Review
test_dsa_indexer.py.test_fp4_mqa_logits_pass_selfsampling_prefill_format_gate.test_top_k.py.test_gvr_selfsampling_topk.py, including alignment, padding, short rows, ties, odd widths, slab limits, engine-key separation, guards, warmup idempotence, and capture safety.test_pytorch_model_engine.py.tests/integration/test_lists/,test-db/,qa/, orwaives.txtchanges were identified.